package com.lenovo.redission.redission.config.lock;

import com.lenovo.redission.redission.lock.IDistributedLock;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;


@Log4j2
@Aspect
@Configuration
@ConditionalOnClass(IDistributedLock.class)
@AutoConfigureAfter(DistributedLockAutoConfiguration.class)
public class DistributedLockAspectConfiguration {

    @Autowired(required = false)
    private IDistributedLock redisLock;

    private ExpressionParser parser = new SpelExpressionParser();

    private DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();

    @Pointcut("@annotation(DistributedLock)")
    private void lockPoint() {

    }

    private String parse(String key, Method method, Object[] args) {
        String[] params = discoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < params.length; i++) {
            context.setVariable(params[i], args[i]);
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }

    @Around("lockPoint()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("around:{}", Thread.currentThread().getId());
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
        String key = distributedLock.value();
        if (key.charAt(0) == '#' || key.charAt(0) == '\'' || key.charAt(0) == '"') {
            Object[] args = pjp.getArgs();
            key = parse(key, method, args);
        }
        key = distributedLock.prefix() + key;
        log.info("distributedLock lock {}", distributedLock);
        boolean lock = redisLock.tryLockWait(key, distributedLock.expireTime(), distributedLock.waitTime(), TimeUnit.MILLISECONDS);
        if (!lock) {
            log.info("get lock {} failed", key);
            return null;
        }

        //得到锁,执行方法，释放锁
        log.info("get lock {} success", key);
        try {
            return pjp.proceed();
        } catch (Exception e) {
            throw e;
        } finally {
            redisLock.unlock(key);
            log.info("release lock key {} ", key);
        }
    }
}